package pl.edu.agh.ki.neuralnetwork.network;

import java.util.LinkedList;
import java.util.List;

import pl.edu.agh.ki.neuralnetwork.exceptions.NotEnoughLayersException;
import pl.edu.agh.ki.neuralnetwork.exceptions.NotEnoughNeuronsException;
import pl.edu.agh.ki.neuralnetwork.exceptions.OutOfRangeException;
import pl.edu.agh.ki.neuralnetwork.exceptions.ResultNotReadyException;
import pl.edu.agh.ki.neuralnetwork.layer.Layer;
import pl.edu.agh.ki.neuralnetwork.neurons.InnerNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.InputNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.Neuron;

public class NeuralNetworkImpl implements NeuralNetwork {

    private List<Layer<InnerNeuron> > innerLayers;
    private Layer<InputNeuron> inputLayer;
    private List<Double> output;
    
    public NeuralNetworkImpl(Layer<InputNeuron> inputLayer, List<Layer<InnerNeuron>> innerLayers ) throws NotEnoughLayersException, NotEnoughNeuronsException {
        if(innerLayers.size() < 1)
            throw new NotEnoughLayersException();
        for(Layer<InnerNeuron> layer : innerLayers)
            if(layer.size() < 1)
                throw new NotEnoughNeuronsException();
        this.innerLayers = innerLayers;
        if(inputLayer.size() < 1)
            throw new NotEnoughNeuronsException();
        
        this.inputLayer = inputLayer;
        this.output = null;
    }

    public void compute() {
    	resetOutput();
        Layer<InnerNeuron> lastLayer = innerLayers.get(innerLayers.size()-1);
        this.output = new LinkedList<Double>();
        for(InnerNeuron neuron : lastLayer)
            this.output.add(neuron.getOutput());

    }

    public int getInputSize() {
        return innerLayers.get(0).size();
    }

    public Double getOutputSignal(int no) throws OutOfRangeException, ResultNotReadyException {
        if(output == null)
            throw new ResultNotReadyException();
        if(no >= output.size())
            throw new OutOfRangeException();
        
        return output.get(no);
    }

    public Double[] getOutputSignals() throws ResultNotReadyException {
        if(output == null)
            throw new ResultNotReadyException();
        
        return (Double[])output.toArray(new Double[output.size()]);
    }

    public int getOutputSize() {
        return innerLayers.get(innerLayers.size()-1).size();
    }

    public void setInputSignal(int no, double val) throws OutOfRangeException {
        if(inputLayer.size() <= no)
            throw new OutOfRangeException();
        inputLayer.get(no).setInput(val);
    }

    public void setInputSignals(Double[] s) {
        int i=0;
        for(InputNeuron neuron : inputLayer) {
            neuron.setInput(s[i]);
            i++;
        }
    }

    public Layer getLayer(int no) throws OutOfRangeException {
        if(no == 0)
            return inputLayer;
        if(no > innerLayers.size())
            throw new OutOfRangeException();
        return innerLayers.get(no-1);
    }

    public int getLayersNumber() {
        return innerLayers.size() + 1;
    }
    
    public void resetOutput() {
    	for(Neuron neuron : inputLayer)
    		neuron.reset();
    	for(Layer<InnerNeuron> layer : innerLayers)
    		for(Neuron neuron : layer)
    			neuron.reset();
    }

	public void learn(List<Double> learningInput, List<Double> pattern,int iteration) {
		System.out.println("unimplemented");
	}
    
}